import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Calendar;
import java.util.Random;
import java.util.StringTokenizer;

/*
 * This is the Handler for the client when a connection to the server is made
 */
public class HandleClient extends Thread{

	Socket clientSocket = null;
	BufferedReader in = null;
	PrintWriter out = null;
	boolean portInUse = false;
	String operation = " ";
	int reqNo = 0;
	int failCodes[] = {1, 2, 3, 4, 5, 6,7};
	boolean terminate = false;
	int startFile = 0;
	int Nmax = 0;
	String fileToPut = " ";
	int numChunks = 0;
	int priority  = 0;
	boolean isTriggered = false;
	CentralServerInterface centralServer = null; 
	String centralServerhostName = "";
	String rmiServer = "CentralServer";
	String serverPort = "";
	String myHostName = "";
	
	//Constructor to initialize a socket connection
	
	public HandleClient(Socket pSocket, int port, String pHostName) throws IOException, NotBoundException 
	{
		clientSocket = pSocket;
		out = new PrintWriter(clientSocket.getOutputStream(), true);
		in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
		serverPort = ""+port;
		myHostName = pHostName;
		centralServerhostName  = Server.centralServerhostName;
	}
	  
	/*
	 * reads from the socket for initial requests like the 
	 * hello, get, put, aput and aget
	 */

	private void ReadFromSocket() throws IOException, InterruptedException, NotBoundException 
	{
		
	    String xFromClient = in.readLine();
		
		System.out.println("Client said " + xFromClient);
		
		ParseRequest(xFromClient);
				
		if(operation.equalsIgnoreCase("hello"))
		{
			HelloHandler();
		}
		else if(operation.equalsIgnoreCase("list"))
		{
			ListHandler();
		}
		else if(operation.contains("delete")||operation.contains("Delete"))
		{
			
		}
		
		// After a put/get operation is done
		// the lock on the file should be updated
		
		else if(operation.equalsIgnoreCase("put"))
		{
			PutHandler(false);
			centralServer = (CentralServerInterface) Naming.lookup ("//" + centralServerhostName + "/"+rmiServer);
			centralServer.mUpdateLock(fileToPut, myHostName + " " +serverPort,true);
			
		}
		else if(operation.equalsIgnoreCase("get"))
		{
			
			GetHandler(false);
			centralServer = (CentralServerInterface) Naming.lookup ("//" + centralServerhostName + "/"+rmiServer);
			centralServer.mUpdateLock(fileToPut, myHostName + " " +serverPort,false);
			
		}
		else if(operation.equalsIgnoreCase("aget"))
		{
			GetHandler(true);
			//centralServer = (CentralServerInterface) Naming.lookup ("//" + hostName + "/"+rmiServer);
			//centralServer.mUpdateLock(fileToPut,hostName + " " +serverPort);
			
		}
		else if(operation.equalsIgnoreCase("aput"))
		{
			PutHandler(true);
			//centralServer = (CentralServerInterface) Naming.lookup ("//" + hostName + "/"+rmiServer);
			//centralServer.mUpdateLock(fileToPut,hostName + " " +serverPort);
			
			
		}
		else if(operation.contains("bye")||operation.contains("Bye"))
		{
			ByeHandler();
		}
		
	}
	/*
	 * Handler for the Get Request, 
	 * The Get request handles both the get and pull requests 
	 * Main issue to watch out for is to check if the file requested by the user is present
	 * or not
	 */
	
	private void GetHandler(boolean isAbort) throws IOException 
	{
		
		String xToClient = new String();
		Random r = new Random();
		int xfileNo = r.nextInt(10);
		
		if(!CheckFileList())
		{
			// File Not found, Send Failure
			xToClient =  "get" + " " + reqNo + " " + fileToPut + " FAILURE" + failCodes[5];
			out.println(xToClient);
			return;
		}
		xToClient =  "get" + " " + reqNo + " " + fileToPut + " \"READY\" " + xfileNo;
		out.println(xToClient);
		
		PullHandler(isAbort);
	
		//Server.mTriggerThreads(fileToPut);
	}



	private void PullHandler(boolean isAbort) throws IOException 
	{

		
		String xheader = "pull "+ reqNo + " SUCCESS ";
		int xReadBytes = 0;
		int xStartByte = 0;
		boolean isLast = false;
		
		FileInputStream xfis = new FileInputStream(Server.directoryName + "/" + fileToPut);
		DataOutputStream xdos = new DataOutputStream(clientSocket.getOutputStream());
		
		File  file_obj=new File(Server.directoryName + "/" + fileToPut);
		long xFileSize=file_obj.length();
		
		String xFromClient = in.readLine();
		String xStr[] = xFromClient.split(" ");
		int chunkSize = Integer.parseInt(xStr[4]);
		byte [] xbyteFile  = new byte [chunkSize];
		
		

		while((xReadBytes = xfis.read(xbyteFile, 0, chunkSize)) != -1)
		{
			if(xReadBytes < chunkSize|| (xStartByte+xReadBytes)==xFileSize){

				xheader += "LAST "+ xStartByte + " " + xReadBytes;
				isLast = true;
			}
			else{

				xheader  += "NOTLAST " + xStartByte + " " + xReadBytes;
				xStartByte += xReadBytes;
			} 
			out.println(xheader);
			//xdos.write(xbyteFile);
			xdos.write(xbyteFile, 0, xReadBytes);
			if(!isLast)
			{
				xFromClient = in.readLine();
				xStr = xFromClient.split(" ");
				if(xStr[0].equalsIgnoreCase("abort"))
				{
					xheader = "abort " + reqNo;
					out.println(xheader);
					break;
				}
				chunkSize = Integer.parseInt(xStr[4]);
				xbyteFile  = new byte [chunkSize];
			}

			xheader = "pull " + reqNo +" SUCCESS ";
		}	
		xfis.close();
		
	}

	private boolean CheckFileList() 
	{
		
		File[] xFileList = Server.GetFileList();
		for(int i = 0;i<xFileList.length;++i){
			if(fileToPut.equalsIgnoreCase(xFileList[i].getName())){
				return true;
			}
		}
		return false;
	}

/*
 * Handler for the put requests
 * 
 */

	private void PutHandler(boolean pisAbort) throws IOException 
	{
				
		Random r = new Random();
		int xfileNo = r.nextInt(10);
				
		String xToClient = "put "+ reqNo + " " + fileToPut + " \"READY\" " + xfileNo ;
		
		System.out.println(xToClient);
		out.println(xToClient);
		
		PushHandler(pisAbort);
		
		//Server.mTriggerThreads(fileToPut);
	}
	
	


	private void PushHandler(boolean pisAbort) throws IOException {
			
		boolean xdone = false;
		String xTmpFile = Server.directoryName + "/tmp" + Calendar.getInstance().getTimeInMillis();;
		FileOutputStream xfos = new FileOutputStream(xTmpFile);
		DataInputStream xdis = new DataInputStream(clientSocket.getInputStream());
		
		while(!xdone){
			
			String xFromClient = in.readLine();
			String xToClient = "push "+ reqNo + " " + fileToPut;
			
			//System.out.println(xFromClient);
			
			String[] xStr = xFromClient.split(" ");
			if(xStr[0].equalsIgnoreCase("abort"))
			{
				xToClient = "abort " + reqNo;
				out.println(xToClient);
				pisAbort = true;
				break;
			}
			int xlength = Integer.parseInt(xStr[5]);
			byte[] xbyteClient = new byte[xlength];
			xdis.readFully(xbyteClient);//read data from socket
			xfos.write(xbyteClient);
			xToClient += " SUCCESS " + xlength;
			out.println(xToClient);

			if(xStr[3].equalsIgnoreCase("LAST"))
			{
				xdone = true;
			}
		}
		xfos.close();
		
		if(!pisAbort){
			File xf = new File(xTmpFile);
			xf.renameTo(new File(Server.directoryName + "/" + fileToPut));
		}
		else{
			File xf = new File(xTmpFile);
			xf.delete();
		}
	}

	/*
	 * List the files in the directory 
	 */
	private void ListHandler() throws IOException {
		
		File[] xList = Server.GetFileList();
		int xnumFiles = 0;
		String xToClient = " ";
		String xFileNames = " ";
		String xStatus = " ";
		if(startFile >= xList.length){
			//failure
			xStatus = "{FAILURE} " + 7;
		}
		else {
			if((startFile+Nmax) > xList.length){

				xnumFiles = xList.length;
				xStatus  = "{SUCCESS} ";
			}
			else {
				xnumFiles = Nmax;
				xStatus = "{SUCCESS} ";
			}
			
			for(int i = startFile;i<xnumFiles;++i){
				xFileNames += xList[i].getName() + " ";
				System.out.println("FileName " + xList[i].getName());

			}
		}
		xToClient = "list " + reqNo + " " + startFile + " " + xnumFiles + " " + xFileNames  + xStatus ;
		out.println(xToClient);
		
	}

	/*
	 * The bye handles the terminate requests from the client
	 */
	private void ByeHandler() throws IOException {
		
		String xToClient = " ";
		
		Server.isBusy = false;
		
		xToClient = "bye " + reqNo;
		out.println(xToClient);
		terminate = true;
	}
	/*
	 * The connection starts from a hello, the server is designed to accept requests from 
	 * multiple clients but sends a failure notice to the other clients
	 */

	private void HelloHandler() throws IOException {
		String xToClient = "hello " + reqNo +" ";
		
		xToClient += "{SUCCESS}";
		
		out.println(xToClient);
	}

	private void ParseRequest(String pString) 
	{
		
		StringTokenizer xTok = new StringTokenizer(pString);
		
		operation = xTok.nextToken();
		String str = xTok.nextToken();
		reqNo = Integer.parseInt(str);
		if(operation.equalsIgnoreCase("list"))
		{
			startFile = Integer.parseInt(xTok.nextToken());
			Nmax = Integer.parseInt(xTok.nextToken());
		}
		else if(operation.equalsIgnoreCase("put")|| operation.equalsIgnoreCase("aput")||operation.equalsIgnoreCase("get")||operation.equalsIgnoreCase("aget"))
		{
			fileToPut = xTok.nextToken();
			//priority = Integer.parseInt(xTok.nextToken());
			if(operation.equalsIgnoreCase("aput")||operation.equalsIgnoreCase("aget")){
				numChunks = Integer.parseInt(xTok.nextToken());
			}
		}
		
		
	}
	public void run()
	{
				try 
				{
					ReadFromSocket();
				} 
				catch (Exception e) 
				{
					e.printStackTrace();
				} 
				
				System.out.println("Exited the function ");
	}

}
